Skip to content

Switch container networking from host to named bridge networks - #403

Closed
pablomh wants to merge 10 commits into
theforeman:masterfrom
pablomh:foremanctl_networks
Closed

Switch container networking from host to named bridge networks#403
pablomh wants to merge 10 commits into
theforeman:masterfrom
pablomh:foremanctl_networks

Conversation

@pablomh

@pablomh pablomh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Currently all containers run with network: host, which means they
share the host network namespace and communicate via localhost. This
works but provides no network isolation between services.

This PR replaces host networking with named Podman bridge networks:

  • foreman-db (internal, isolated) — PostgreSQL; accessible only to
    services that need database access
  • foreman-cache (internal, isolated) — Valkey; accessible only to
    services that need cache access
  • foreman-app — Foreman, Pulp, Candlepin, httpd; shared application
    network
  • foreman-proxy — Foreman and the smart proxy; used when the
    foreman-proxy feature is enabled
  • iop-core-network (10.130.0.0/24) — the IOP services, when the
    iop feature is enabled; the IOP gateway is dual-homed onto this
    network and foreman-app so Foreman can reach it directly

Services communicate by container DNS name (e.g. postgresql,
candlepin, iop-core-gateway) rather than localhost. A dedicated
TLS certificate with SAN=candlepin is issued for the Candlepin
container, since Foreman now connects to it via the bridge DNS name
and validates the hostname. The IOP gateway's certificate covers both
localhost and iop-core-gateway so the same endpoint keeps working
from the host and from the bridge network.

PostgreSQL is accessed over its Unix socket rather than TCP/localhost
wherever a client and the database container share a host (backup,
health checks, and the internal-database roles); this avoids
publishing a database port at all when nothing outside the host needs
TCP access to it.

Candlepin does not publish any ports to the host — Foreman reaches
it directly over the bridge. Port publishing is minimised throughout:
PostgreSQL and Valkey never publish ports; Pulp API and content
endpoints publish to 127.0.0.1 for the httpd proxy; the smart
proxy publishes to 0.0.0.0 for external clients.

The foreman-proxy deployment always registers the smart proxy with
Foreman under its own real FQDN (independent of the bridge-network
split). --registration-url remains available as a separate,
optional override for load-balancer or multi-proxy setups: when set,
it's written into the proxy's own settings as the endpoint newly
registering hosts should use, without changing how Foreman itself
manages or validates the proxy.

Containers inherit host /etc/hosts entries via Podman's
base_hosts_file setting, so host-only name mappings remain usable
from inside containers even though they're no longer on the host
network.

The old-installer-answers migration path (foremanctl migrate) now
also rewrites an internal database host of localhost/127.0.0.1 to
the postgresql container DNS name, since a bridge-networked
deployment can no longer reach the database container via loopback.

The development environment keeps network: host for
postgresql/valkey/candlepin/pulp to avoid reconfiguring every
service URL for a Rails development workflow where the Foreman
process runs directly on the host.

@pablomh
pablomh force-pushed the foremanctl_networks branch from 6ab3c71 to 7800f9d Compare March 9, 2026 09:36
@pablomh
pablomh marked this pull request as draft March 9, 2026 17:08
@pablomh pablomh changed the title Switch container networking from host to named bridge networks EXPERIMENT: Switch container networking from host to named bridge networks Mar 9, 2026
@ehelms

ehelms commented Mar 9, 2026

Copy link
Copy Markdown
Member

I like where you are headed with security in mind and isolation where possible. I worry about a few things:

  1. Complexity for the developer at the start having to understand this network topology and debugging.
  2. Service complexity for services that might need to be connected to multiple networks due to how many services they use.
  3. Is there an impact to the end user due to network requirements?

I have been wondering and exploring how to use podman networking to our advantage to try and avoid TLS between services. Based on your experiments, what do you think about that?

Do you think you could craft a version of this that starts with just a single network as our deployment model?

The addition of iop also adds network (https://github.com/theforeman/foremanctl/pull/280/changes#diff-024ced7a24ad97c3e89dceb9ebae521f47807987db81039cd4973f04b79e7e9c) since it's based on puppet-iop and the current model. The goal being similar to what I think you are thinking to achieve and it'd be nice to bring this all together into a single design.

@ekohl

ekohl commented Mar 17, 2026

Copy link
Copy Markdown
Member

I agree with @ehelms that using host networking was always a hack to get us moving forward sooner and this is a proper implementation. In my own experience, the bridges are subject to firewall rules while most firewalls allow all traffic on localhost. That is something to consider in our installation guides.

  • foreman-db (internal, isolated) — PostgreSQL; accessible only to
    services that need database access

  • foreman-cache (internal, isolated) — Redis; accessible only to
    services that need cache access

All Foreman services need both the DB and Redis so why not merge those into a single foreman-internal net?

  • foreman-app — Foreman, Pulp, Candlepin, httpd; has a gateway for
    outbound traffic

If we merge #118 then all httpd -> {Foreman, Pulp} traffic goes over unix sockets. Then i'm not sure if this is needed.

In my view I'd start top down. We have 4 services:

  • Foreman
  • Foreman Proxy
  • Pulp
  • Candlepin

Each of those should have its own internal network for private traffic (if any) and isolated from each other.

Then I'd look at incoming and outgoing traffic.

  • Foreman needs to talk to all 4 and "the internet" (or an external HTTP proxy)
  • Foreman Proxy needs to talk to Foreman and Pulp
  • Pulp needs to talk to "the internet" (or an external HTTP proxy)
  • Not sure what Candlepin needs

pablomh added a commit to pablomh/foremanctl that referenced this pull request Mar 17, 2026
…-app with foreman-internal

All backend services (PostgreSQL, Redis, Candlepin, Pulp, Foreman,
Dynflow) now share a single isolated internal network. This addresses
the feedback in PR theforeman#403:

- ehelms: start with a single network model to reduce complexity
- ekohl: merge foreman-db and foreman-cache since all services need both

foreman-proxy-net is kept separate as it serves a distinct purpose
(Foreman Proxy <-> Foreman communication).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pablomh
pablomh force-pushed the foremanctl_networks branch from 44836b6 to b2e2338 Compare March 18, 2026 17:22
@pablomh

pablomh commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

I've tried to implement @ekohl's "view" because I think that a single network wouldn't provide us with much compared to the current implementation.

@ehelms: regarding your idea of avoid TLS between services, I think that the current zero-trust approach relies on trying to have every communication encrypted, and I very much support that. This implementation is agnostic or the transport use, so I think it would fit both usecases.

I've been playing with adding your IoP work on top of this and for now, it's working.

@pablomh
pablomh force-pushed the foremanctl_networks branch from b2e2338 to f8c5009 Compare March 20, 2026 09:06
@pablomh
pablomh force-pushed the foremanctl_networks branch 3 times, most recently from 1835f65 to f48bf12 Compare March 31, 2026 12:26
@pablomh
pablomh marked this pull request as ready for review March 31, 2026 14:07
@pablomh pablomh changed the title EXPERIMENT: Switch container networking from host to named bridge networks Switch container networking from host to named bridge networks Mar 31, 2026
@ehelms
ehelms marked this pull request as draft July 22, 2026 01:44
@ehelms

ehelms commented Jul 22, 2026

Copy link
Copy Markdown
Member

@pablomh A lot has changed since you opened this, with some things being removed and others being simplified. I moved to draft. If you get time, consider rebasing and updating the design based on the current state.

pablomh and others added 10 commits August 12, 2026 00:58
Introduces a shared role that creates a Podman network with a given
driver/internal/isolation configuration, used by later commits to move
core services off host networking onto dedicated bridge networks.

Netavark 2.0 flipped the bridge driver's isolation default from
opt-in to opt-out (containers/netavark#709), so the role always
passes an explicit isolate value rather than relying on the
installed netavark's own default. Because containers.podman 1.20.2
serializes that boolean incorrectly for Podman 6 + Netavark 2 (until
containers/ansible-podman-collections picks up commit d45819fd6561),
the role falls back to creating/inspecting the network via the podman
CLI directly on newer Podman/Netavark, and only uses the
containers.podman module on older versions.

Co-authored-by: Cursor <cursoragent@cursor.com>
PostgreSQL, Valkey, Candlepin, Foreman, and Pulp move off host
networking onto dedicated bridge networks created by deploy_network:
foreman-db (internal PostgreSQL and its clients), foreman-cache
(Valkey and its clients), and foreman-app (Foreman, Candlepin, Pulp).
Services now reach each other by container DNS name (postgresql,
valkey, candlepin) instead of localhost/host.containers.internal.

PostgreSQL now also listens on a shared Unix socket
(postgresql_socket_dir) in addition to the bridge network, and the
postgresql/check_*/backup roles that ran administrative queries as the
postgres superuser switch to socket-based login rather than
password+host auth, since that socket is reachable from any container
sharing the mount without needing to open localhost access. The backup
role's own database dump connection info still needs a real reachable
host, so it picks the socket for internal databases and the configured
database_host otherwise via a new backup_database_host fact.

Candlepin gets an explicit candlepin_healthcheck_host/hostname pair and
a --resolve-based healthcheck so its container binds 0.0.0.0 while
still presenting the right SNI/Host identity used by its own cert
(added in a later commit). Foreman and Pulp's Redis/Candlepin URLs
move from localhost to their new container DNS names, driven by new
foreman_networks/foreman_migration_networks and
pulp_networks/pulp_migration_networks variables that also add the
foreman-proxy network to Foreman's rake/console containers when that
feature is enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
…n bridge network

foreman-proxy joins a new foreman-proxy bridge network (still also
published on host port 8443 via foreman_proxy_ports) rather than
running on host networking, while keeping its public identity
(foreman_proxy_name/foreman_proxy_url) unchanged: it still registers
with Foreman under its own real FQDN, so this is purely a network
plumbing change for how Foreman reaches the proxy container, not an
identity change. Podman's aardvark-dns needs a moment to reconverge
after the proxy container (re)starts before Foreman can resolve/reach
it again on the bridge, so a new shared wait_for_smart_proxy role
polls the proxy's /v2/features endpoint (from inside the foreman
container, using its client cert) with retries before anything tries
to register or use the proxy.

foreman_proxy's own tasks now restart the container as a plain task
(not a handler notify+flush) immediately before this readiness check
and the initial registration, since flushing handlers here would also
prematurely fire the paired "Refresh Foreman Proxy" handler before
registration has happened. The same readiness check runs again after
the role's normal end-of-role handler flush, since that flush restarts
the container again on effectively every deploy run. The backup role
reuses the same shared wait_for_reachable.yaml task after restarting
foreman.target, for the same reconvergence reason.

Co-authored-by: Cursor <cursoragent@cursor.com>
The IoP core network (iop-core-network) is now created through
deploy_network instead of calling containers.podman.podman_network
directly, so it picks up the same Netavark 2+ isolation-default
compatibility handling as the other bridge networks: it must not be
isolated from foreman-app/foreman-proxy, which it needs to reach.

The gateway container is dual-homed onto both iop-core-network and
foreman-app (still also published on host loopback at 24443), and
registers with Foreman using its container DNS name/port
(iop_core_gateway_registration_url) instead of localhost:24443,
waiting for that endpoint to become reachable from Foreman first
(mirroring foreman_proxy's own bridge-network readiness handling).
DB-consuming IoP services (advisor, inventory, remediation, vmaas,
vulnerability) join foreman-db alongside iop-core-network when the
database is internal, via a new iop_database_networks variable, and
iop_database_host switches from host.containers.internal to the
postgresql container DNS name in that mode.

iop_fdw's and iop_inventory's direct PostgreSQL admin queries (FDW
server/user-mapping setup, inventory schema/view creation) move from
password+host login to the same postgresql_socket_dir Unix socket the
other administrative roles now use, since containers sharing that
mount can reach it without depending on network reachability to the
database container.

Co-authored-by: Cursor <cursoragent@cursor.com>
Now that most services run on bridge networks instead of host
networking, containers need a way to resolve host-only name mappings
that operators define locally (e.g. in /etc/hosts) rather than through
real DNS. Podman's base_hosts_file setting copies the host's
/etc/hosts entries into each container's own /etc/hosts at container
start, which keeps those host-only mappings usable from the smart
proxy and other containers without reintroducing host networking.

Co-authored-by: Cursor <cursoragent@cursor.com>
Candlepin is now reachable from Foreman as https://candlepin:23443
rather than https://localhost:23443, so it needs a server certificate
valid for the candlepin hostname instead of sharing the localhost
certificate. certificates_hostnames gains candlepin, and
candlepin_tomcat_key/candlepin_tomcat_certificate now point at the
newly issued candlepin cert/key pair (candlepin_key/
candlepin_certificate in vars/certificates.yml) rather than the
localhost ones.

The IoP gateway keeps using the localhost certificate (it is still
reachable at both localhost and, once dual-homed onto foreman-app,
iop-core-gateway), so certificates now support a
certificates_hostname_aliases map of extra SAN names per issued
hostname. When the iop feature is enabled, base.yaml adds
iop-core-gateway as an alias for the localhost certificate so
Foreman's client-cert validation of the gateway succeeds over the
bridge network too.

Co-authored-by: Cursor <cursoragent@cursor.com>
foreman-installer answers migrated with an internal (db_manage)
database used to carry loopback hosts like localhost or 127.0.0.1,
which matched the installer's own host-networked PostgreSQL. Now that
internal PostgreSQL runs on the foreman-db bridge network as the
postgresql container, migrate_answers rewrites those loopback values
to postgresql for internal database_mode, leaving external database
hosts untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
Development deployments run the Rails process directly on the host,
so postgresql/valkey/candlepin/pulp keep network: host and their
localhost-based URLs there rather than adopting the new bridge
networks, avoiding a parallel set of development-only service URL
rewrites. remote-database.yaml, which stands up a standalone
PostgreSQL instance, needs the same explicit postgresql_network: host
override now that the role's own default changed to foreman-db.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a "Container networking" section to docs/developer/deployment.md
describing the four bridge networks, how services reach each other by
container DNS name, the foreman-proxy public/internal identity split,
why development deployments stay on host networking, and the
base_hosts_file DNS fallback. Update docs/iop.md's architecture,
service table, database, and certificate sections to reflect the
gateway's dual-homed networking and the internal database's bridge
access.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adjust the remaining feature/integration tests to match services no
longer being reachable via host networking: PostgreSQL and Valkey are
no longer expected to have host-published ports (asserting instead
that the PostgreSQL Unix socket exists and that Valkey responds via
podman exec), Candlepin's status/TLS checks now run curl and openssl
s_client from inside the foreman container against the candlepin
container DNS name instead of curling localhost from the host, and the
webhook listener fixture targets host.containers.internal since
containers can no longer reach the host via localhost.

Remote execution tests now install the foreman-proxy container's SSH
key into the client's authorized_keys via a new
remote_execution_authorized_proxy_key fixture and verify the client is
DNS-resolvable from the proxy container, since the proxy's SSH-based
remote execution now runs from a bridge-networked container rather
than the host.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pablomh
pablomh force-pushed the foremanctl_networks branch from f48bf12 to cc66858 Compare August 12, 2026 06:21
@pablomh
pablomh marked this pull request as ready for review August 12, 2026 06:22
@pablomh

pablomh commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @ekohl, @ehelms! Based on the comments in #728, I feel like this approach is not the desired one so I'm closing it.

@pablomh pablomh closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants